// 操作接口类型

/**
 * ==========================================
 * Partial 工具类型可以将一个类型的所有属性变为可选的，
 * 且该工具类型返回的类型是给定类型的所有子集
 * ==========================================
*/
type TPartial<T> = {
    [P in keyof T]?: T[P];  
};
interface IPerson {
    name: string;
    age?: number;
    weight?: number;  
}
/**
 * type TPartialPerson = {
        name?: string | undefined;
        age?: number | undefined;
        weight?: number | undefined;
    }
*/
type TPartialPerson = TPartial<IPerson>;
/**
 * 相当于
    interface TPartialPerson {
        name?: string;
        age?: number;
        weight?: number;
    }
*/

/**
 * ==========================================
 * Required 工具类型可以将给定类型的所有属性变为必填的
 * - 与 ? 组合起来表示去除类型的可选属性，表示必填
 * ==========================================
*/
type TRequired<T> = {
    [P in keyof T]-?: T[P];
};
/**
 * type RequiredPerson = {
        name: string;
        age: number;
        weight: number;
    }
*/
type RequiredPerson = TRequired<IPerson>;
/**
 * 相当于  
    interface RequiredPerson {
        name: string;
        age: number;
        weight: number;
    }
*/


/**
 * ==========================================
 * Readonly 工具类型可以将给定类型的所有属性设为只读
 * ==========================================
*/
type TReadonly<T> = {
    readonly [P in keyof T]: T[P]
}
/**
 * type ReadonlyPerson = {
        readonly name: string;
        readonly age?: number | undefined;
        readonly weight?: number | undefined;
    }
*/
type ReadonlyPerson = TReadonly<IPerson>
/**
 * 相当于
 * interface ReadonlyPerson = {
    readonly name: string;
    readonly age?: number | undefined;
    readonly weight?: number | undefined;
}
*/


/**
 * ==========================================
 * Pick 工具类型可以从给定的类型中选取出指定的键值，然后组成一个新的类型
 * ==========================================
*/
type TPick<T, K extends keyof T> = {
    [P in K]: T[P]
}
/**
 * type NewIPerson = {
        name: string;
        age?: number | undefined;
    }
*/
type NewIPerson = TPick<IPerson, 'name' | 'age'>
/**
 * // 相当于
    interface NewPerson {
        name: string;
        age?: number;
    }
*/


/**
 * ==========================================
 * Omit 工具类型的功能是返回去除指定的键值之后返回的新类型
 * ==========================================
*/
type TOmit<T, K extends keyof any> = TPick<T, Exclude<keyof T, K>>
/**
 * type AnotherIPerson = {
        name: string;
        weight?: number | undefined;
    }
*/
type AnotherIPerson = TOmit<IPerson, 'age'>
/**
 * // 相当于
    interface NewPerson {
        name: string;
        age?: number;
    }
*/


/**
 * ==========================================
 * Exclude 从联合类型中去除指定的类型
 * ==========================================
*/
type TExclude<T, U> = T extends U ? never : T
// type TestT = "b" | "c"
type TestExclude = TExclude<'a' | 'b' | 'c', 'a'>



/**
 * ==========================================
 * Extract 从联合类型中提取指定的类型
 * ==========================================
*/
type TExtract<T, U> = T extends U ? T : never
// type TestExtract = "a"
type TestExtract = TExtract<'a' | 'b' | 'c', 'a'>


/**
 * 获取接口类型交集
*/
type TIntersect<T, U> = {
    [K in TExtract<keyof T, keyof U>]: T[K]
}
interface NextIPerson {
    name: string;
    school: string;
    age?: number;
}
/**
 * type NextTIntersect = {
        name: string;
        age: number | undefined;
    }
*/
type NextTIntersect = TIntersect<IPerson, NextIPerson>



/**
 * ==========================================
 * NonNullable 从联合类型中去除 null 或者 undefined 的类型
 * ==========================================
*/
type TNonNullable<T> = T extends null | undefined ? never : T
type OtherTNonNullable<T> = TExclude<T, null | undefined>
// type TestNonNullable = string | number
type TestNonNullable = TNonNullable<string | number | undefined | null>



/**
 * ==========================================
 * Record 生成接口类型
 * ==========================================
*/
type TRecord<K extends keyof any, T> = {
    [P in K]: T
}
type TMenuKey = 'home' | 'about' | 'more'
interface IMenu {
    label: string
    hidden?: boolean
}
const menus: TRecord<TMenuKey, IMenu> = {
    about: { label: '关于'},
    home: { label: '主页' },
    more: { label: '更多', hidden: true },
}
console.log(menus)



/**
 * ==========================================
 * ConstructorParameters 获取构造函数的构造参数
 * ConstructorParameters 类型的实现则需要使用 infer 关键字推断构造参数的类型
 * ==========================================
*/
type TConstructorParameters<T extends new (...args: any) => any> = T extends new(...args: infer P) => any ? P : never;
class IConstructPerson {  
    constructor(name: string, age?: number) {}
}
// [name: string, age?: number]  
type TestTConstructorParameters = TConstructorParameters<typeof IConstructPerson>;



/**
 * ==========================================
 * Parameters 用来获取函数的参数并返回序对
 * ==========================================
*/
type TParameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never;
// type T0 = []
type TestParameters0 = TParameters<() => void>;
// type T1 = [x: number, y?: string | undefined]
type TestParameters1 = TParameters<(x: number, y?: string) => void>;



/**
 * ==========================================
 * ReturnType 用来获取函数的返回类型
 * ==========================================
*/
type TReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;
// type TestReturn0 = void
type TestReturn0 = TReturnType<() => void>;
// type TestReturn0 = void
type TestReturn1 = TReturnType<() => string>;


/**
 * ==========================================
 * ThisParameterType 用来获取函数的 this 参数类型
 * ==========================================
*/
type TThisParameterType<T> = T extends (this: infer U, ...args: any[]) => any ? U : unknown;
// type TestThisParameterType = Number
type TestThisParameterType = TThisParameterType<(this: Number, x: number) => void>;



/**
 * ==========================================
 * ThisType 用来在对象字面量中指定 this 的类型
 * ==========================================
*/
type ObjectDescriptor<D, M> = {
    data?: D;
    methods?: M & ThisType<D & M>; // methods 中 this 的类型是 D & M
};
function makeObject<D, M>(desc: ObjectDescriptor<D, M>): D & M {
    let data: object = desc.data || {};
    let methods: object = desc.methods || {};
    return { ...data, ...methods } as D & M;
}
const obj = makeObject({
    data: { x: 0, y: 0 },
    methods: {
        moveBy(dx: number, dy: number) {
            this.x += dx; // this => D & M
            this.y += dy; // this => D & M
        },
    },
});
obj.x = 10;
obj.y = 20;
obj.moveBy(5, 5);
